using Google.Protobuf;
using LiteNetLib;
using MongoDB.Bson;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using UnityEngine;

public interface INetEventBridge
{
    long Id { get; set; }
    void Initialze();
    void HandleMessage(int msgId, Google.Protobuf.IMessage msg);
}

public partial class ClientSession
{
    public long id;
    public IPEndPoint endPoint;
    public INetEventBridge eventBridge;

    public void Create()
    {
        eventBridge.Initialze();
    }

    public void HandleMessage(int msgId,Google.Protobuf.IMessage msg)
    {
        eventBridge.HandleMessage(msgId, msg);
    }

    public void Destroy()
    {

    }
}

public interface IRaceLogic
{
    int Id { get; set; }
    void Update();
}

//
public partial class ServerNetManager
{
    static int s_race_id = 2000;
    System.Func<IRaceLogic> raceFactory;
    Dictionary<int, IRaceLogic> id2races = new Dictionary<int, IRaceLogic>(64);

    public IRaceLogic StartNewRace()
    {
        IRaceLogic logic = raceFactory();
        logic.Id = ++s_race_id;
        id2races.Add(s_race_id, logic);
        return logic;
    }

    public IRaceLogic getRaceLogic(int raceId)
    {
        if(id2races.TryGetValue(raceId,out var logic))
        {
            return logic;
        }
        return null;
    }

    public IRaceLogic getRaceLogic(long playerId)
    {
        if(!TryGetPlayerRoom(playerId,out var roomInfo))
        {
            return null;
        }

        return getRaceLogic(roomInfo.raceId);
    }
}


/// <summary>
/// Room Server Logic
/// </summary>
public partial class ServerNetManager
{
    static int s_room_id = 1000;
    public class RoomInfo
    {
        public int Id;
        public string name;
        public string password;
        public int raceId;
        public List<long> playerIds;
    }
    Dictionary<int,RoomInfo> roomInfos = new Dictionary<int,RoomInfo>(1024);
    Dictionary<long, int> playerId2RoomId = new Dictionary<long, int>(1024);

    public bool TryGetPlayerRoom(long playerId,out RoomInfo roomInfo)
    {
        if(playerId2RoomId.TryGetValue(playerId,out int roomId) && roomInfos.TryGetValue(roomId,out roomInfo))
        {
            return true;
        }

        roomInfo = new RoomInfo
        {
            Id = 0,
            name = string.Empty,
            raceId = 0,
        };
        return false;
    }

    public void SyncRoomListInfo(long sessionId)
    {
        Map.ResRoomListInfoMessage msg = ServerMessagePool.Instance.Fetch<Map.ResRoomListInfoMessage>();
        msg.RoomInfos.Clear();
        foreach (var roomId in roomInfos.Keys)
        {
            var roomBrief = getRoomBrief(roomId);
            if(null != roomBrief)
            {
                msg.RoomInfos.Add(roomBrief);
            }
        }
        ServerNetManager.Instance.Send(sessionId, msg);
        foreach (var roomInfo in msg.RoomInfos)
        {
            ServerMessagePool.Instance.Recycle(roomInfo);
        }
        msg.RoomInfos.Clear();
        ServerMessagePool.Instance.Recycle(msg);
    }

    public class PlayerInfo
    {
        public string userName;
        public long playerId;
        public int roleId;
    }

    Dictionary<long, PlayerInfo> sessionId2PlayerId = new Dictionary<long, PlayerInfo>(32);
    Dictionary<long, long> playerId2SessionId = new(32);

    void TryDisConnectToPlayer(long playerId)
    {
        if (playerId2SessionId.TryGetValue(playerId,out var sessionId))
        {
            service.Remove(sessionId);
            playerId2SessionId.Remove(playerId);
            sessionId2PlayerId.Remove(sessionId);
        }
    }

    public void RegistePlayer(long sessionId,string userName,long playerId,int roleId)
    {
        TryDisConnectToPlayer(playerId);

        sessionId2PlayerId[sessionId] = new PlayerInfo
        {
            userName = userName,
             playerId = playerId,
             roleId = roleId,
        };
    }

    Map.RoomBrief getRoomBrief(int roomId)
    {
        if (roomInfos.TryGetValue(roomId, out var roomInfo))
        {
            Map.RoomBrief brief = ServerMessagePool.Instance.Fetch<Map.RoomBrief>();
            brief.RoomId = roomId;
            brief.RoomName = roomInfo.name;
            brief.RoomState = roomInfo.raceId;
            brief.PlayerCount = roomInfo.playerIds.Count;
            brief.HasPassword = !string.IsNullOrEmpty(roomInfo.password);
            return brief;
        }
        return null;
    }

    public void SyncAddRoomInfo(int roomId)
    {
        var brief = getRoomBrief(roomId);
        if(null != brief)
            ServerNet.SendResRoomAddMessage(0, brief);
    }

    public PlayerInfo GetPlayer(long sessionId)
    {
        if (sessionId2PlayerId.TryGetValue(sessionId, out var playerInfo))
            return playerInfo;
        return null;
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="playerId"></param>
    /// <param name="roomName"></param>
    /// <returns></returns>
    public int CreateRoom(long playerId,string roomName,string roomPassword)
    {
        if(playerId2RoomId.ContainsKey(playerId))
        {
            //Already In Room
            return -10002;
        }

        if(string.IsNullOrEmpty(roomName))
        {
            //room Name Can Not Be Empty
            return -10003;
        }

        int roomId = ++s_room_id;
        roomInfos.Add(roomId,new RoomInfo
        {
            Id = roomId,
            name = roomName,
            password = roomPassword,
            playerIds = new List<long>(8),
        });

        playerId2RoomId.Add(playerId,roomId);

        return roomId;
    }

    public int LeaveRoom(long playerId)
    {
        if(!playerId2RoomId.TryGetValue(playerId,out int roomId))
        {
            return -10004;
        }

        if(!roomInfos.TryGetValue(roomId,out var roomInfo))
        {
            playerId2RoomId.Remove(playerId);
            return -10005;
        }

        roomInfo.playerIds.Remove(playerId);
        playerId2RoomId.Remove(playerId);
        if(roomInfo.playerIds.Count == 0)
        {
            RemoveRoom(roomInfo.Id);
        }

        return 0;
    }

    public void RemoveRoom(int roomId)
    {
        if(roomInfos.ContainsKey(roomId))
        {
            var room = roomInfos[roomId];
            foreach (var player in room.playerIds)
            {
                playerId2RoomId.Remove(player);
                room.playerIds.Clear();
            }
            roomInfos.Remove(roomId);
        }
    }
}

public partial class ServerNetManager
{
    static ServerNetManager s_instance = new ServerNetManager();
    public static ServerNetManager Instance => s_instance;

    AService service;
    CodedInputStream inputStream;
    byte[] outputBuffer;
    CodedOutputStream outputStream;
    System.Func<INetEventBridge> netEventFactory;

    public void Awake(System.Func<IRaceLogic> raceFactory,System.Func<INetEventBridge> netEventFactory,string ip,int port)
    {
        this.raceFactory = raceFactory;
        this.netEventFactory = netEventFactory;
        service = new TService(new IPEndPoint(IPAddress.Parse(ip),port),ServiceType.Outer);
        service.AcceptCallback = this.OnAccept;
        service.ReadCallback = this.OnRead;
        service.ErrorCallback = this.OnError;
        connections = new Dictionary<IPEndPoint, ClientSession>(32);
        inputStream = new CodedInputStream(System.Array.Empty<byte>());
        outputBuffer = new byte[4096];
        outputStream = new CodedOutputStream(outputBuffer);
        sessions = new Dictionary<long, ClientSession>(32);
    }

    Dictionary<IPEndPoint, ClientSession> connections;
    Dictionary<long, ClientSession> sessions;

    void OnError(long id,int errCode)
    {
        UnityEngine.Debug.LogError($"[{id}] player Has disconnected");
        service.Remove(id);
        var session = sessions[id];
        session.Destroy();
        sessions.Remove(id);
    }

    void OnRead(long id, MemoryBuffer memoryBuffer)
    {
        if(sessions.TryGetValue(id,out var session))
        {
            Debug.Log($"OnRead:{id} === memoryBuffer Length = {memoryBuffer.Length - memoryBuffer.Position}");
            var buf = memoryBuffer.GetBuffer();
            int msgId = buf.getMsgId();
            inputStream.Reset(buf, 8, (int)memoryBuffer.Length);
            if (!ServerMessageManager.TryGetMsgType(msgId, out var msgType))
            {
                Debug.LogError($"Message With Id = {msgId} Has Not Registered Yet !!");
                return;
            }
            var msg = ServerMessagePool.Instance.Fetch(msgType);
            msg.MergeFrom(inputStream);
            session.HandleMessage(msgId,msg);
            ServerMessagePool.Instance.Recycle(msg);
        }
        service.Recycle(memoryBuffer);
    }

    void OnAccept(long id, IPEndPoint endPoint)
    {
        if(connections.TryGetValue(endPoint,out ClientSession session))
        {
            UnityEngine.Debug.LogError("Player Has Connected");
            var channel = ((TService)service).Get(session.id);
            if(null != channel)
            {
                ((TService)service).Remove(id);

                var oldSession = sessions[id];
                oldSession.Destroy();
                sessions.Remove(id);
            }
            connections.Remove(endPoint);
        }

        var bridge = this.netEventFactory();
        bridge.Id = id;
        session = new ClientSession
        {
            id = id,
            endPoint = endPoint,
            eventBridge = bridge,
        };
        session.Create();
        connections.Add(endPoint, session);
        sessions.Add(id, session);

        Debug.Log($"[Server]:[OnAccept]:[{endPoint}]");
    }

    public void SendToAll(Google.Protobuf.IMessage msg)
    {
        if (ServerMessageManager.TryGetMsgId(msg.GetType(), out var msgId))
        {
            outputBuffer.putMsgId(msgId);
            outputStream.Reset(this.outputBuffer, 8, this.outputBuffer.Length - 8);
            msg.WriteTo(outputStream);
            int msgLength = (int)outputStream.Position;
            outputBuffer.putMsgLength(msgLength);

            var memoryBuffer = service.Fetch();
            memoryBuffer.Write(outputBuffer, 0, msgLength);
            memoryBuffer.Seek(0, System.IO.SeekOrigin.Begin);

            ((TService)service).SendToAll(memoryBuffer);
        }
        else
        {
            Debug.LogError($"[Server]:Msg({msg.GetType()}) Has Not Handler");
        }
    }

    public void Send(long id,Google.Protobuf.IMessage msg)
    {
        if(id == 0)
        {
            SendToAll(msg);
            return;
        }
        if (ServerMessageManager.TryGetMsgId(msg.GetType(), out var msgId))
        {
            outputBuffer.putMsgId(msgId);
            outputStream.Reset(this.outputBuffer, 8, this.outputBuffer.Length - 8);
            msg.WriteTo(outputStream);
            int msgLength = (int)outputStream.Position;
            outputBuffer.putMsgLength(msgLength);

            var memoryBuffer = service.Fetch();
            memoryBuffer.Write(outputBuffer, 0, msgLength);
            memoryBuffer.Seek(0, System.IO.SeekOrigin.Begin);
            service.Send(id, memoryBuffer);
        }
        else
        {
            Debug.LogError($"[Server]:Msg({msg.GetType()}) Has Not Handler");
        }
    }

    public void Update()
    {
        service.Update();

        foreach(var race in id2races.Values)
        {
            race.Update();
        }
    }

    public void Destroy()
    {
        service.Dispose();
    }
}
